#include <jni.h>
#include <stdlib.h>
#include <stdio.h>
#include <android/log.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <dlfcn.h>
#include <elf.h>


#define TAG "jni-test" //定义TAG

#define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,TAG,__VA_ARGS__) // 定义LOGD类型
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__)
#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)

#define PUBLIC_KEY "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC9m+K0Y96HcQpL58Jb8yXe0nehd71wOufi1J9j/i38+n2TtrQUX4Zd2bRfcbBzE4sF3cQ70QFnnbvg84dK4dpi4T9vEePDUwpFv62OxdHE01bb1SsXBxmPjfpdJMWIK8efq+/lJIB/zRlcGFfnCVGhJD8MVdX+5KOsjtg9IjHmewIDAQAB"//RSA公钥

// 4：案场 5：房江湖
#define SING_KEY_ANCHANG "cPsr50ffuPYBBzQztm8SdiUWbCp9YBSC8PRsJWFCuGcYa8axpi2gKNgV5/fylDlSLdFblkljafHqPkXFAULLRtoW/hU0Oh6hiafC39n+lftmbV0Z3Im7aZfA48iFAfWESyd6O8AcmdoI6MgTC0gEMkmW2pxSKWyWw5dYf2pz9Ag=" //案场加密key

#define SING_KEY_FJH "e6JYncDIic67SrV8Z1Q3Fk0OWUiak9jFEDJHRL8+d6Ds295UEmeR70JtCYIByxlgCLVSyrHhHuoXQ88dEy8I37MeWt2Y9TvqYj2lsks6dLS+fJhj/V0Ja2nuedbawi8jDk8KBRe+UGRCZc+DKef6bSKjXYQusq3ykEwYG4TFHfY=" //fjh加密Key

#define AES_KEY "allincelinkisno1" //存sp加密 主要用来加密password token，必须16位?


#define STR_LEN 6//定义随机输出的字符串长度。


const char HexCode[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
                        'F'};

JNIEXPORT jstring JNICALL
GetSignStr(JNIEnv *env, jobject context) {
    jclass tem_class;
    jmethodID tem_method;
    jclass class_context = (*env)->GetObjectClass(env, context);
    // PackageInfo localPackageInfo = context.getPackageManager()
    // .getPackageInfo(context.getPackageName(), 64);
    tem_method = (*env)->GetMethodID(env, class_context, "getPackageManager",
                                     "()Landroid/content/pm/PackageManager;");
    jobject obj_package_manager = (*env)->CallObjectMethod(env, context, tem_method);
    // getPackageName
    tem_method = (*env)->GetMethodID(env, class_context, "getPackageName", "()Ljava/lang/String;");
    jobject obj_package_name = (*env)->CallObjectMethod(env, context, tem_method);
    // getPackageInfo
    tem_class = (*env)->GetObjectClass(env, obj_package_manager);
    tem_method = (*env)->GetMethodID(env, tem_class, "getPackageInfo",
                                     "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
    jobject obj_package_info = (*env)->CallObjectMethod(env, obj_package_manager, tem_method,
                                                        obj_package_name, 64);

    // Signature[] arrayOfSignature = localPackageInfo.signatures;
    // Signature localSignature = arrayOfSignature[0];
    tem_class = (*env)->GetObjectClass(env, obj_package_info);
    jfieldID fieldID_signatures = (*env)->GetFieldID(env, tem_class, "signatures",
                                                     "[Landroid/content/pm/Signature;");
    jobjectArray signatures = (*env)->GetObjectField(env, obj_package_info, fieldID_signatures);
    jobject signature = (*env)->GetObjectArrayElement(env, signatures, 0);
    // localSignature.toByteArray()
    tem_class = (*env)->GetObjectClass(env, signature);
    tem_method = (*env)->GetMethodID(env, tem_class, "toByteArray", "()[B");
    jobject obj_sign_byte_array = (*env)->CallObjectMethod(env, signature,
                                                           tem_method);// 这个就是拿到的签名byte数组

    // MessageDigest localMessageDigest = MessageDigest.getInstance("MD5");
    jclass class_MessageDigest = (*env)->FindClass(env, "java/security/MessageDigest");
    tem_method = (*env)->GetStaticMethodID(env, class_MessageDigest, "getInstance",
                                           "(Ljava/lang/String;)Ljava/security/MessageDigest;");
    jobject obj_md5 = (*env)->CallStaticObjectMethod(env, class_MessageDigest, tem_method,
                                                     (*env)->NewStringUTF(env, "md5"));
    // localMessageDigest.update(localSignature.toByteArray());
    //tem_class = (*env)->GetObjectClass(env, obj_md5);
    tem_method = (*env)->GetMethodID(env, class_MessageDigest, "update",
                                     "([B)V");// 这个函数的返回值是void，写V
    (*env)->CallVoidMethod(env, obj_md5, tem_method, obj_sign_byte_array);
    // localMessageDigest.digest()
    tem_method = (*env)->GetMethodID(env, class_MessageDigest, "digest", "()[B");
    // 这个是md5以后的byte数组，现在只要将它转换成16进制字符串，就可以和之前的比较了
    jobject obj_array_sign = (*env)->CallObjectMethod(env, obj_md5, tem_method);// jni中有强转类型的概念吗
    // // 这个就是签名的md5值
    // String str2 = toHex(localMessageDigest.digest());

    // 尝试用c写一下：http://blog.csdn.net/pingd/article/details/41945417
    jsize int_array_length = (*env)->GetArrayLength(env, obj_array_sign);
    jbyte *byte_array_elements = (*env)->GetByteArrayElements(env, obj_array_sign, JNI_FALSE);
//    char *char_result = (char *) malloc(
//            int_array_length * 2 + 1);// 开始没有+1，在有的情况下会越界产生问题，还是在后面补上\0比较好
    // 将byte数组转换成16进制字符串，发现这里不用强转，jbyte和unsigned char应该字节数是一样的
//    ByteToHexStr(byte_array_elements, char_result, int_array_length);
//    *(char_result + int_array_length * 2) = '\0';// 在末尾补\0
//    jstring string_result = (*env)->NewStringUTF(env, char_result);
//    // release
//    (*env)->ReleaseByteArrayElements(env, obj_array_sign, byte_array_elements, JNI_ABORT);
//    // 释放指针使用free
//    free(char_result);
    char hex_sha[int_array_length * 2 + 1];
    int i;
    for (i = 0; i < int_array_length; ++i) {
        hex_sha[2 * i] = HexCode[((unsigned char) byte_array_elements[i]) / 16];
        hex_sha[2 * i + 1] = HexCode[((unsigned char) byte_array_elements[i]) % 16];
    }
    hex_sha[int_array_length * 2] = '\0';
    jstring string_result = (*env)->NewStringUTF(env, hex_sha);
    return string_result;

    // 如果使用java的16进制转换的话，应该也可以的，用c写16进制转换太麻烦
    //jclass class_HandleData = (*env)->FindClass(env, "com/hengbao/util/HandleData");
    //tem_method = (*env)->GetStaticMethodID(env, class_HandleData, "bytesToHexString1", "([B)Ljava/lang/String;");
    //jobject obj_result = (*env)->CallStaticObjectMethod(env, class_HandleData, tem_method, obj_array_sign);
    //return obj_result;
}

void exitApplication(JNIEnv *env, jint flag) {
    jclass temp_clazz = NULL;
    jmethodID mid_static_method;
    // 1、从classpath路径下搜索ClassMethod这个类，并返回该类的Class对象
    temp_clazz = (*env)->FindClass(env, "java/lang/System");
    mid_static_method = (*env)->GetStaticMethodID(env, temp_clazz, "exit", "(I)V");
    (*env)->CallStaticVoidMethod(env, temp_clazz, mid_static_method, flag);
    (*env)->DeleteLocalRef(env, temp_clazz);
}







JNIEXPORT jboolean JNICALL
Java_com_homelink_newlink_libbase_util_VsckManager_loadVSFromC(JNIEnv *env, jclass jcls, jobject context,
                                              jint channel) {

    //1.获得实例对应的类
//    jclass jcls = (*env)->GetObjectClass(env, jobj);
    //2.通过class类找到对应的method id
    //用类方法可以省略1、2步骤

//    jmethodID getCommentStr = (*env)->GetStaticMethodID(env, jcls, "getCommentStr",
//                                                        "(Ljava/lang/Object;)Ljava/lang/String;");
//    if (getCommentStr == NULL) {
//        return NULL;
//    }
    //3.方案一、将代码传入的context赋值给方法 读取comment中存的加密串
//    jstring signKey = (*env)->CallStaticObjectMethod(env, jcls, getCommentStr, context);
    //3.方案二、转换so里存的加密串
    jstring signKey;
    if (channel == 4) {
        signKey = (*env)->NewStringUTF(env, SING_KEY_ANCHANG);
    } else if (channel == 5) {
        signKey = (*env)->NewStringUTF(env, SING_KEY_FJH);
    }
//    LOGD("----signkey----");
    //4.将singkey Base64 先解密
    jmethodID base64Dec = (*env)->GetStaticMethodID(env, jcls, "base64Dec",
                                                    "(Ljava/lang/String;)[B");
    if (base64Dec == NULL) {
        return JNI_FALSE;
    }
    jbyteArray decKey = (jbyteArray) (*env)->CallStaticObjectMethod(env, jcls, base64Dec, signKey);
//    LOGD("----decKey----");
    //5.再用公钥解密 此处必须直接传byte[] 不能转成string后再获取byte
    jmethodID getFinalStr = (*env)->GetStaticMethodID(env, jcls, "getFStr",
                                                      "(Ljava/lang/String;[B)Ljava/lang/String;");
    if (getFinalStr == NULL) {
        return JNI_FALSE;
    }
    //将 char转换成 jString
    jstring publicKey = (*env)->NewStringUTF(env, PUBLIC_KEY);
    //存储的签名
    jstring finalStr = (*env)->CallStaticObjectMethod(env, jcls, getFinalStr, publicKey,
                                                      decKey);
//    LOGD("----finalStr----");
    //APP现在签名
    jstring fromAppStr = GetSignStr(env, context);
    //将获取的字符串转换为char *
    const char *finalJavaStr = (*env)->GetStringUTFChars(env, finalStr, JNI_FALSE);
    const char *fromAppJavaStr = (*env)->GetStringUTFChars(env, fromAppStr, JNI_FALSE);
    int resultCode;
    resultCode = strcmp(finalJavaStr, fromAppJavaStr);
    (*env)->ReleaseStringUTFChars(env, finalStr, finalJavaStr);
    (*env)->ReleaseStringUTFChars(env, fromAppStr, fromAppJavaStr);
    if (resultCode != 0) {
        //签名不一致 调用方法退出程序
        exitApplication(env, 0);
        return JNI_FALSE;
    } else {
        return JNI_TRUE;
    }

}
///////////////////////////////反调试相关/////////////////////////////////////////////////////////////////


//获取TracePid
int getnumberfor_str(char* str){
    if(str == NULL){
        return -1;
    }
    char result[20];
    int count = 0;
    while(*str != '\0'){
        if(*str >= 48 && *str <= 57){
            result[count] = *str;
            count++;
        }
        str++;
    }
    int val = atoi(result);
    return val;
}

//开启循环轮训检查TracePid字段
void* thread_fuction() {
    int pid = getpid();
    char file_name[20] = {'\0'};
    sprintf(file_name, "/proc/%d/status",pid);

    char linestr[256];
    int i=0, traceid;
    FILE *fp;
    while(1){
        i = 0;
        fp = fopen(file_name,"r");
        if(fp == NULL){
            break;
        }
        while(!feof(fp)){
            fgets(linestr, 256, fp);
            if(i == 5){
                traceid = getnumberfor_str(linestr);
//                LOGD("traceId:%d", traceid);
                if(traceid > 0){
//                    LOGD("I was be traced...trace pid:%d",traceid);
                    exit(0);
                }
                break;
            }
            i++;
        }
        fclose(fp);
        sleep(5);
    }

}

void* checkTracerPid()
{
    char fileName[50] = {0};
    char buffer[512] = {0};
    int pid = getpid();
    sprintf(fileName, "/proc/%d/status", pid);
    FILE *fd = fopen(fileName, "r");
    if (fd) {
        while (fgets(buffer, 512, fd)) {
            if (strstr(buffer, "TracerPid")) {
                if (atoi(&buffer[10])) {//atoi将字符串转换成整数  任何非0的值被当做真
                    fclose(fd);
                    raise(SIGTERM); //发送终止信号请求
                }
            }
        }
    }
    fclose(fd);
}


void create_thread_check_traceid(){
    pthread_t t_id;
    int err = pthread_create(&t_id,NULL, checkTracerPid, NULL);
    if(err != 0 ) {
        LOGD("create thread fail: %s\n",strerror(err));
    }
}


/*
 * Set some test stuff up.
 *
 * Returns the JNI version on success, -1 on failure.
 */
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    LOGD("JNI on load...");

    //自己附加
//    LOGD("ptrace myself...");
//    ptrace(PTRACE_TRACEME, 0, 0, 0);

    //声明变量
    jint result = JNI_ERR;
    JNIEnv* env = NULL;

    //检测自己有没有被trace
    create_thread_check_traceid();
    //获取JNI环境对象
    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_6) != JNI_OK) {
        LOGD("ERROR: GetEnv failed\n");
        return JNI_ERR;
    }
    result = JNI_VERSION_1_6;
    return result;
}

//password加密本地存储
JNIEXPORT jstring JNICALL
Java_com_homelink_newlink_libbase_util_VsckManager_encP2local(JNIEnv *env, jclass jcls, jstring p_) {

    if (p_ == NULL) {
        return NULL;
    }

    jstring key = (*env)->NewStringUTF(env, AES_KEY);

    jmethodID p2local = (*env)->GetStaticMethodID(env, jcls, "p2local",
                                                  "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
    if (p2local == NULL) {
        return JNI_FALSE;
    }

    jstring value = (*env)->CallStaticObjectMethod(env, jcls, p2local, p_, key);

    return value;
}


//password本地存储解密
JNIEXPORT jstring JNICALL
Java_com_homelink_newlink_libbase_util_VsckManager_decLocal2P(JNIEnv *env, jclass jcls, jstring src_) {
    if(src_ == NULL){
        return NULL;
    }

    jstring key = (*env)->NewStringUTF(env, AES_KEY);

    jmethodID local2p = (*env)->GetStaticMethodID(env, jcls, "local2p",
                                                     "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
    if (local2p == NULL) {
        return JNI_FALSE;
    }
    jstring value = (*env)->CallStaticObjectMethod(env, jcls, local2p, src_, key);

    return value;
}

//网络传pwd加密
JNIEXPORT jstring JNICALL
Java_com_homelink_newlink_libbase_util_VsckManager_encLP2Net(JNIEnv *env, jclass jcls, jstring src_) {

    if (src_ == NULL) {
        return NULL;
    }

    //随机生成字符串
//    char str[STR_LEN + 1] = {0};
//    int i, flag;
//
//    srand(time(NULL));//通过时间函数设置随机数种子，使得每次运行结果随机。
//    for (i = 0; i < STR_LEN; i++) {
//        flag = rand() % 3;
//        switch (flag) {
//            case 0:
//                str[i] = rand() % 26 + 'a';
//                break;
//            case 1:
//                str[i] = rand() % 26 + 'A';
//                break;
//            case 2:
//                str[i] = rand() % 10 + '0';
//                break;
//        }
//    }

//    char *buf = (char*)malloc(10);
//    strcpy(buf, "123456");
    //随机生成的6位字符串进行base64加密
//    jstring randomStr = (*env)->NewStringUTF(env,buf);

    //随机加密串
    jmethodID getStr = (*env)->GetStaticMethodID(env, jcls, "getRStr",

                                                    "()Ljava/lang/String;");
    if(getStr == NULL){
        return JNI_FALSE;
    }
    jstring randomStr = (*env)->CallStaticObjectMethod(env,jcls,getStr);


    jmethodID base64Enc = (*env)->GetStaticMethodID(env, jcls, "base64Enc",
                                                    "(Ljava/lang/String;)Ljava/lang/String;");
    if(base64Enc == NULL){
        return JNI_FALSE;
    }
    const char *base64RanChar = (*env)->GetStringUTFChars(env,randomStr,JNI_FALSE);

//    LOGD("-----randomStr----%s\n",base64RanChar);

    //密码加密
    jstring enStr = (*env)->CallStaticObjectMethod(env,jcls,base64Enc,src_);
    const char *encStrChar = (*env)->GetStringUTFChars(env,enStr,JNI_FALSE);

//    LOGD("-----encStrChar----%s\n",encStrChar);

    //分成两段
    int len = 4;
    strlen(base64RanChar);
    //暂设50位  pwd
    char start[50], end[len + 1];
    memset(start, '\0', sizeof(start));
    strncpy(start, base64RanChar, len);
    memset(end, '\0', sizeof(end));
    strncpy(end, base64RanChar + (strlen(base64RanChar) - len), len);
    //start + pwd + end
    strcat(start,encStrChar);
    strcat(start,end);

    jstring finalStr = (*env)->NewStringUTF(env,start);
//    LOGD("-----finalStr----:%s\n",finalStr);
    //最终串加密
    jstring finalEncStr = (*env)->CallStaticObjectMethod(env,jcls,base64Enc,finalStr);
//    LOGD("-----finalEncStr----%s\n",finalEncStr);


    (*env)->ReleaseStringUTFChars(env,randomStr,base64RanChar);
    (*env)->ReleaseStringUTFChars(env, enStr, encStrChar);
    return finalEncStr;
}